/**
 * MOAB, a Mesh-Oriented datABase, is a software component for creating,
 * storing and accessing finite element mesh data.
 *
 * Copyright 2004 Sandia Corporation.  Under the terms of Contract
 * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government
 * retains certain rights in this software.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <H5Tpublic.h>
#include <H5Gpublic.h>
#include <H5Dpublic.h>
#include <H5Spublic.h> /* for H5S_MAX_RANK */
#include <H5Apublic.h>
#include <H5Ppublic.h>
#include "status.h"
#include "file-handle.h"
#include "mhdf.h"
#include "util.h"
#include "names-and-paths.h"

hid_t mhdf_getNativeType( hid_t input_type, int size, mhdf_Status* status )
{
    H5T_sign_t sgn;
    H5T_class_t cls;
    hid_t tmp_id, type_id;

    mhdf_setOkay( status );

    cls = H5Tget_class( input_type );
    switch( cls )
    {
        case H5T_FLOAT:
            switch( size )
            {
                case 4:
                    return H5T_NATIVE_FLOAT;
                case 8:
                    return H5T_NATIVE_DOUBLE;
                case 16:
                    return H5T_NATIVE_LDOUBLE;
                default:
                    mhdf_setFail( status, "Invalid size for floating point type: %d", size );
                    return -1;
            }

        case H5T_INTEGER:
            sgn = H5Tget_sign( input_type );
            if( H5T_SGN_ERROR == sgn )
            {
                mhdf_setFail( status, "Internall errror calling H5Tget_sign." );
                return -1;
            }
            if( sizeof( char ) == size )
                return sgn == H5T_SGN_NONE ? H5T_NATIVE_UCHAR : H5T_NATIVE_SCHAR;
            else if( sizeof( short ) == size )
                return sgn == H5T_SGN_NONE ? H5T_NATIVE_USHORT : H5T_NATIVE_SHORT;
            else if( sizeof( int ) == size )
                return sgn == H5T_SGN_NONE ? H5T_NATIVE_UINT : H5T_NATIVE_INT;
            else if( sizeof( long ) == size )
                return sgn == H5T_SGN_NONE ? H5T_NATIVE_ULONG : H5T_NATIVE_LONG;
            else if( (int)H5Tget_size( H5T_NATIVE_LLONG ) == size )
                return sgn == H5T_SGN_NONE ? H5T_NATIVE_ULLONG : H5T_NATIVE_LLONG;

            mhdf_setFail( status, "Invalid size for integer type: %d", size );
            return -1;

        case H5T_ENUM:
            tmp_id = H5Tget_super( input_type );
            if( tmp_id < 0 )
            {
                mhdf_setFail( status, "Internal error calling H5Tget_super." );
                return -1;
            }
            type_id = mhdf_getNativeType( tmp_id, size, status );
            H5Tclose( tmp_id );
            return type_id;

        case H5T_TIME:
        case H5T_OPAQUE:
        case H5T_REFERENCE:
            mhdf_setFail( status, "Unsupported type class." );
            return -1;

        case H5T_COMPOUND:
        case H5T_VLEN:
        case H5T_ARRAY:
        case H5T_STRING:
            mhdf_setFail( status, "Only atomic types are supported." );
            return -1;

        default:
            mhdf_setFail( status, "Internal error calling H5Tget_class.  Bad handle?" );
            return -1;
    }
}

static hid_t get_tag( mhdf_FileHandle file_handle, const char* tag_name, hid_t* id_type, mhdf_Status* status )
{
    hid_t group_id, tag_id;
    char* path;
    FileHandle* file_ptr;

    file_ptr = (FileHandle*)file_handle;
    if( !mhdf_check_valid_file( file_ptr, status ) ) return -1;

    if( NULL != id_type ) *id_type = file_ptr->id_type;

    path = mhdf_name_to_path_copy( tag_name, status );
    if( NULL == path ) return -1;

#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
    group_id = H5Gopen2( file_ptr->hdf_handle, TAG_GROUP, H5P_DEFAULT );
#else
    group_id = H5Gopen( file_ptr->hdf_handle, TAG_GROUP );
#endif
    if( group_id < 0 )
    {
        mhdf_setFail( status, "Failed to open tag group." );
        free( path );
        return -1;
    }

#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
    tag_id = H5Gopen2( group_id, path, H5P_DEFAULT );
#else
    tag_id   = H5Gopen( group_id, path );
#endif
    H5Gclose( group_id );
    free( path );
    if( tag_id < 0 )
    {
        mhdf_setFail( status, "Failed to open tag data for tag \"%s\".", tag_name );
        return -1;
    }

    mhdf_setOkay( status );
    return tag_id;
}

static hid_t get_tag_type( FileHandle* file_ptr, const char* tag_path, mhdf_Status* status )
{
    hid_t group_id, tag_id, type_id;

#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
    group_id = H5Gopen2( file_ptr->hdf_handle, TAG_GROUP, H5P_DEFAULT );
#else
    group_id = H5Gopen( file_ptr->hdf_handle, TAG_GROUP );
#endif
    if( group_id < 0 )
    {
        mhdf_setFail( status, "Failed to open tag group." );
        return -1;
    }

#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
    tag_id = H5Gopen2( group_id, tag_path, H5P_DEFAULT );
#else
    tag_id   = H5Gopen( group_id, tag_path );
#endif
    H5Gclose( group_id );
    if( tag_id < 0 )
    {
        mhdf_setFail( status, "Failed to open group for tag \"%s\".", tag_path );
        return -1;
    }

#if defined( H5Topen_vers ) && H5Topen_vers > 1
    type_id = H5Topen2( tag_id, TAG_TYPE_NAME, H5P_DEFAULT );
#else
    type_id  = H5Topen( tag_id, TAG_TYPE_NAME );
#endif
    H5Gclose( tag_id );
    if( type_id < 0 )
    {
        mhdf_setFail( status, "Failed to open type data for tag \"%s\".", tag_path );
        return -1;
    }

    return type_id;
}

/** Helper function to write default and mesh values for tag
 *\param tag_id       The file object upon which to attach the attribute
 *\param attrib_name  The name of the attribute object
 *\param type_id      The data type of the attribute data
 *\param value        Pointer to attribute data
 *\param value_size   Size of attribute data, as multiple of type indicated
 *                    by type_id.  Should be 1 except for variable-length tag data.
 */
static int store_tag_val_in_attrib( hid_t tag_id,
                                    const char* attrib_name,
                                    hid_t type_id,
                                    const void* value,
                                    hsize_t value_size,
                                    mhdf_Status* status )
{
    hid_t write_type;
    int rval;
    if( value_size == 1 )
        write_type = type_id;
    else if( H5Tget_class( type_id ) == H5T_OPAQUE )
    {
        write_type = H5Tcreate( H5T_OPAQUE, value_size );
    }
    else
    {
#if defined( H5Tarray_create_vers ) && H5Tarray_create_vers > 1
        write_type = H5Tarray_create2( type_id, 1, &value_size );
#else
        write_type = H5Tarray_create( type_id, 1, &value_size, 0 );
#endif
    }

    if( write_type < 0 )
    {
        mhdf_setFail( status, "Error constructing type object for tag mesh/default value" );
        return -1;
    }

    rval = mhdf_create_scalar_attrib( tag_id, attrib_name, write_type, value, status );
    if( write_type != type_id ) H5Tclose( write_type );

    return rval;
}

static hid_t create_tag_common( mhdf_FileHandle file_handle,
                                const char* tag_name,
                                enum mhdf_TagDataType tag_type,
                                int size,
                                int storage,
                                const void* default_value,
                                int default_value_size_in,
                                const void* global_value,
                                int global_value_size_in,
                                hid_t hdf_type,
                                hid_t hdf_base_type,
                                mhdf_Status* status )
{
    hid_t temp_id, group_id, tag_id;
    char* path;
    FileHandle* file_ptr;
    herr_t rval;
    hsize_t arr_len;
    int one = 1, var_len = 0;
    hsize_t default_value_size = default_value_size_in;
    hsize_t global_value_size  = global_value_size_in;
    int close_base_type        = 0;

    /* Force standard data types over user-specified types */

    if( tag_type != mhdf_OPAQUE ) hdf_type = 0;
    if( tag_type != mhdf_ENTITY_ID ) hdf_base_type = 0;

    /* Validate input */

    file_ptr = (FileHandle*)file_handle;
    if( !mhdf_check_valid_file( file_ptr, status ) ) return -1;

    if( !tag_name || !*tag_name )
    {
        mhdf_setFail( status, "Invalid tag name" );
        return -1;
    }

    /* Open the tag group */

#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
    group_id = H5Gopen2( file_ptr->hdf_handle, TAG_GROUP, H5P_DEFAULT );
#else
    group_id = H5Gopen( file_ptr->hdf_handle, TAG_GROUP );
#endif
    if( group_id < 0 )
    {
        mhdf_setFail( status, "H5Gopen(\"%s\") failed.", TAG_GROUP );
        return -1;
    }

    /* Create path string for tag object */

    path = mhdf_name_to_path_copy( tag_name, status );
    if( !path )
    {
        H5Gclose( group_id );
        return -1;
    }

    /* Create group for this tag */

#if defined( H5Gcreate_vers ) && H5Gcreate_vers > 1
    tag_id = H5Gcreate2( group_id, path, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT );
#else
    tag_id   = H5Gcreate( group_id, path, 3 );
#endif
    if( tag_id < 0 )
    {
        mhdf_setFail( status, "H5Gcreate( \"%s\" ) failed.", path );
        free( path );
        H5Gclose( group_id );
        return -1;
    }

    /* Store the tag name as the comment on the group entry */

    rval = H5Gset_comment( group_id, path, tag_name );
    H5Gclose( group_id );
    free( path );
    if( rval < 0 )
    {
        mhdf_setFail( status, "H5Gset_comment failed for tag \"%s\"", tag_name );
        H5Gclose( tag_id );
        return -1;
    }

    /* Store TSTT tag type as attribute */

    rval = mhdf_create_scalar_attrib( tag_id, TAG_TYPE_ATTRIB, H5T_NATIVE_INT, &storage, status );
    if( !rval )
    {
        H5Gclose( tag_id );
        return -1;
    }

    if( hdf_type )
    {
        hdf_type = H5Tcopy( hdf_type );
        arr_len  = 1;
    }
    else
    {
        switch( tag_type )
        {
            default:
            case mhdf_OPAQUE:
                arr_len  = 1;
                hdf_type = H5Tcreate( H5T_OPAQUE, abs( size ) );
                H5Tset_tag( hdf_type, "tag_data" );
                break;

            case mhdf_BITFIELD:
                arr_len = 1;
                if( size <= 0 )
                {
                    mhdf_setFail( status, "Invalid size (%d) for bit tag.", (int)size );
                    return -1;
                }
                else if( size <= 8 )
                    hdf_type = H5Tcopy( H5T_NATIVE_B8 );
                else if( size <= 16 )
                    hdf_type = H5Tcopy( H5T_NATIVE_B16 );
                else if( size <= 32 )
                    hdf_type = H5Tcopy( H5T_NATIVE_B32 );
                else if( size <= 64 )
                    hdf_type = H5Tcopy( H5T_NATIVE_B64 );
                else
                {
                    mhdf_setFail( status, "Cannot create a bit tag larger than 64-bits.  %d bits requested.\n",
                                  (int)size );
                    return -1;
                }

                if( 0 > H5Tset_precision( hdf_type, size ) )
                {
                    mhdf_setFail( status, "H5Tset_precision failed." );
                    return -1;
                }
                break;

            case mhdf_ENTITY_ID:
                arr_len  = abs( size );
                hdf_type = H5Tcopy( H5T_NATIVE_ULONG );
                break;

            case mhdf_BOOLEAN:
                arr_len  = abs( size );
                hdf_type = H5Tcopy( H5T_NATIVE_UCHAR );
                break;

            case mhdf_INTEGER:
                arr_len  = abs( size );
                hdf_type = H5Tcopy( H5T_NATIVE_INT );
                break;

            case mhdf_FLOAT:
                arr_len  = abs( size );
                hdf_type = H5Tcopy( H5T_NATIVE_DOUBLE );
                break;
        }
    }

    if( hdf_type <= 0 )
    {
        mhdf_setFail( status, "Failed to create tag type object." );
        H5Gclose( tag_id );
        return -1;
    }

    if( hdf_base_type && H5Tget_class( hdf_type ) != H5Tget_class( hdf_base_type ) )
    {
        mhdf_setFail( status, "Invalid base type for tag default/global data" );
        H5Gclose( tag_id );
        return -1;
    }

    if( size < -1 || !arr_len )
    {
        mhdf_setFail( status, "Invalid 'size' parameter passed to mhdf_createTag (%d)", (int)size );
        H5Gclose( tag_id );
        return -1;
    }
    else if( size == -1 )
    {
        /* Note: we don't do anything with this here.  We rely on
         *       the app to ask us to create the index table later.
         */
        arr_len = 1;
        /* need to know this later, when storing default/global values */
        var_len = 1;
    }
    else if( arr_len > 1 )
    {
#if defined( H5Tarray_create_vers ) && H5Tarray_create_vers > 1
        temp_id = H5Tarray_create2( hdf_type, 1, &arr_len );
#else
        temp_id = H5Tarray_create( hdf_type, 1, &arr_len, NULL );
#endif
        H5Tclose( hdf_type );
        if( temp_id < 0 )
        {
            mhdf_setFail( status, "Failed to create tag type object." );
            H5Gclose( tag_id );
            return -1;
        }
        hdf_type = temp_id;

        if( hdf_base_type )
        {
            if( H5Tequal( hdf_base_type, hdf_type ) > 0 )
            {
                hdf_base_type = hdf_type;
            }
            else
            {
#if defined( H5Tarray_create_vers ) && H5Tarray_create_vers > 1
                temp_id = H5Tarray_create2( hdf_base_type, 1, &arr_len );
#else
                temp_id = H5Tarray_create( hdf_base_type, 1, &arr_len, NULL );
#endif
                if( temp_id < 0 )
                {
                    mhdf_setFail( status, "Failed to create tag type object." );
                    H5Gclose( tag_id );
                    H5Tclose( hdf_type );
                    return -1;
                }
                hdf_base_type   = temp_id;
                close_base_type = 1;
            }
        }
    }

    if( !hdf_base_type ) hdf_base_type = hdf_type;

        /* Create tag type object, or write attribute if opaque */

#if defined( H5Tcommit_vers ) && H5Tcommit_vers > 1
    rval = H5Tcommit2( tag_id, TAG_TYPE_NAME, hdf_type, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT );
#else
    rval     = H5Tcommit( tag_id, TAG_TYPE_NAME, hdf_type );
#endif
    if( rval < 0 )
    {
        mhdf_setFail( status, "H5Tcommit failed for tag \"%s\"", tag_name );
        if( close_base_type ) H5Tclose( hdf_base_type );
        H5Tclose( hdf_type );
        H5Gclose( tag_id );
        return -1;
    }

    /* If tag is entity handle, make note of it */
    if( tag_type == mhdf_ENTITY_ID )
    {
        rval = mhdf_create_scalar_attrib( tag_id, TAG_HANDLE_TYPE_ATTRIB, H5T_NATIVE_INT, &one, status );
        if( !rval )
        {
            if( close_base_type ) H5Tclose( hdf_base_type );
            H5Gclose( tag_id );
            H5Tclose( hdf_type );
            return -1;
        }
    }

    /* Store the default value as a attribute of the tag group */

    if( default_value )
    {
        rval = store_tag_val_in_attrib( tag_id, TAG_DEFAULT_ATTRIB, hdf_base_type, default_value,
                                        var_len ? default_value_size : 1, status );
        if( !rval )
        {
            if( close_base_type ) H5Tclose( hdf_base_type );
            H5Gclose( tag_id );
            H5Tclose( hdf_type );
            return -1;
        }
    }

    /* Store global tag value as attribute */

    if( global_value )
    {
        rval = store_tag_val_in_attrib( tag_id, TAG_GLOBAL_ATTRIB, hdf_base_type, global_value,
                                        var_len ? global_value_size : 1, status );
        if( !rval )
        {
            if( close_base_type ) H5Tclose( hdf_base_type );
            H5Gclose( tag_id );
            H5Tclose( hdf_type );
            return -1;
        }
    }

    if( close_base_type ) H5Tclose( hdf_base_type );
    H5Tclose( hdf_type );
    mhdf_setOkay( status );
    return tag_id;
}

hid_t mhdf_getTagDataType( mhdf_FileHandle file_handle, const char* tag_name, mhdf_Status* status )
{
    FileHandle* file_ptr;
    hid_t result;
    char* path;
    API_BEGIN;

    /* Validate input */

    file_ptr = (FileHandle*)file_handle;
    if( !mhdf_check_valid_file( file_ptr, status ) ) return -1;

    if( !tag_name || !*tag_name )
    {
        mhdf_setFail( status, "Invalid tag name" );
        return -1;
    }

    /* Create path string for tag object */

    path = mhdf_name_to_path_copy( tag_name, status );
    if( !path )
    {
        return -1;
    }

    result = get_tag_type( file_ptr, path, status );

    free( path );
    API_END;
    return result;
}

void mhdf_createTag( mhdf_FileHandle file_handle,
                     const char* tag_name,
                     enum mhdf_TagDataType tag_type,
                     int size,
                     int storage,
                     const void* default_value,
                     const void* global_value,
                     hid_t hdf_type,
                     hid_t hdf_base_type,
                     mhdf_Status* status )
{
    hid_t tag_id;
    API_BEGIN;
    tag_id = create_tag_common( file_handle, tag_name, tag_type, size, storage, default_value, 1, global_value, 1,
                                hdf_type, hdf_base_type, status );
    if( tag_id >= 0 ) H5Gclose( tag_id );
    API_END;
}

void mhdf_createVarLenTag( mhdf_FileHandle file_handle,
                           const char* tag_name,
                           enum mhdf_TagDataType tag_type,
                           int storage,
                           const void* default_value,
                           int default_value_length,
                           const void* global_value,
                           int global_value_length,
                           hid_t hdf_type,
                           hid_t hdf_base_type,
                           mhdf_Status* status )
{
    hid_t tag_id;
    int one = 1;

    API_BEGIN;
    tag_id = create_tag_common( file_handle, tag_name, tag_type, -1, storage, default_value, default_value_length,
                                global_value, global_value_length, hdf_type, hdf_base_type, status );
    if( tag_id >= 0 )
    {
        mhdf_create_scalar_attrib( tag_id, TAG_VARLEN_ATTRIB, H5T_NATIVE_INT, &one, status );
        H5Gclose( tag_id );
    }
    API_END;
}

int mhdf_getNumberTags( mhdf_FileHandle file_handle, mhdf_Status* status )
{
    hid_t group_id;
    hsize_t result;
    FileHandle* file_ptr;
    API_BEGIN;

    /* Validate input */

    file_ptr = (FileHandle*)file_handle;
    if( !mhdf_check_valid_file( file_ptr, status ) ) return -1;

        /* Open the tags group */

#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
    group_id = H5Gopen2( file_ptr->hdf_handle, TAG_GROUP, H5P_DEFAULT );
#else
    group_id = H5Gopen( file_ptr->hdf_handle, TAG_GROUP );
#endif
    if( group_id < 0 )
    {
        mhdf_setFail( status, "H5Gopen(\"%s\") failed", TAG_GROUP );
        return -1;
    }

    /* Get number of objects in tags group */

    if( H5Gget_num_objs( group_id, &result ) < 0 )
    {
        mhdf_setFail( status, "Internal failure calling H5Gget_num_objs." );
        H5Gclose( group_id );
        return -1;
    }

    H5Gclose( group_id );
    mhdf_setOkay( status );
    API_END;
    return (int)result;
}

char** mhdf_getTagNames( mhdf_FileHandle file_handle, int* num_names_out, mhdf_Status* status )
{
    hid_t group_id;
    FileHandle* file_ptr;
    hsize_t count, idx;
    char* name;
    char** result;
    ssize_t size;
    API_BEGIN;

    /* Validate input */

    file_ptr = (FileHandle*)file_handle;
    if( !mhdf_check_valid_file( file_ptr, status ) ) return NULL;

        /* Open the tags group */

#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
    group_id = H5Gopen2( file_ptr->hdf_handle, TAG_GROUP, H5P_DEFAULT );
#else
    group_id = H5Gopen( file_ptr->hdf_handle, TAG_GROUP );
#endif
    if( group_id < 0 )
    {
        mhdf_setFail( status, "H5Gopen(\"%s\") failed", TAG_GROUP );
        return NULL;
    }

    /* Get number of objects in tags group */

    if( H5Gget_num_objs( group_id, &count ) < 0 )
    {
        mhdf_setFail( status, "Internal failure calling H5Gget_num_objs." );
        H5Gclose( group_id );
        return NULL;
    }

    /* No tags? */

    *num_names_out = (int)count;
    if( count == 0 )
    {
        H5Gclose( group_id );
        mhdf_setOkay( status );
        return NULL;
    }

    /* Allocate string array */

    result = (char**)mhdf_malloc( sizeof( char* ) * count, status );
    if( NULL == result )
    {
        H5Gclose( group_id );
        return NULL;
    }

    /* Get names */

    for( idx = 0; idx < count; ++idx )
    {
        size = H5Gget_objname_by_idx( group_id, idx, NULL, 0 );
        if( size < 1 || NULL == ( name = (char*)mhdf_malloc( size + 1, status ) ) )
        {
            while( ( --idx ) > 0 )
                free( result[idx] );
            free( result );
            H5Gclose( group_id );
            mhdf_setFail( status, "Internal failure calling H5Gget_objname_by_idx." );
            return NULL;
        }

        H5Gget_objname_by_idx( group_id, idx, name, size + 1 );
        if( !mhdf_path_to_name( name, name ) )
        {
            mhdf_setFail( status, "Invalid character string in internal file path: \"%s\"\n", name );
            return NULL;
        }
        result[idx] = name;
    }

    H5Gclose( group_id );
    mhdf_setOkay( status );
    API_END;
    return result;
}

static int get_attrib_array_length_handle( hid_t attrib_id )
{
    hid_t type_id;
    int rank;
    hsize_t dims[H5S_MAX_RANK];
    int perm[H5S_MAX_RANK];

    type_id = H5Aget_type( attrib_id );
    switch( H5Tget_class( type_id ) )
    {
        case H5T_NO_CLASS:
            dims[0] = -1;
            break;
        case H5T_OPAQUE:
            dims[0] = H5Tget_size( type_id );
            break;
        case H5T_ARRAY:
#if defined( H5Tget_array_dims_vers ) && H5Tget_array_dims_vers > 1
            (void)perm; /* suppress warning */
            rank = H5Tget_array_dims2( type_id, dims );
#else
            rank = H5Tget_array_dims( type_id, dims, perm );
#endif
            if( rank == 1 )
                break;
            else
                return -1;
        default:
            dims[0] = 1;
            break;
    }

    H5Tclose( type_id );
    return dims[0];
}

/*
static int get_attrib_array_length_index( hid_t object_id, unsigned int index )
{
  hid_t attrib_id;
  int result;

  attrib_id = H5Aopen_idx( object_id, index );
  if (attrib_id < 0)
    return -1;

  result = get_attrib_array_length_handle( attrib_id );
  H5Aclose( attrib_id );
  return result;
}
*/

static int get_attrib_array_length_name( hid_t file, const char* path )
{
    hid_t attrib_id;
    int result;

    attrib_id = H5Aopen_name( file, path );
    if( attrib_id < 0 ) return -1;

    result = get_attrib_array_length_handle( attrib_id );
    H5Aclose( attrib_id );
    return result;
}

void mhdf_getTagInfo( mhdf_FileHandle file_handle,
                      const char* tag_name,
                      enum mhdf_TagDataType* class_out,
                      int* size_out,
                      int* tstt_storage_out,
                      int* have_default_out,
                      int* have_global_out,
                      int* have_sparse_out,
                      mhdf_Status* status )
{
    hid_t tag_id, type_id, super_id;
    int i, rval, is_handle;
    hsize_t size, sup_size;
    unsigned int idx;
    int rank, var_data;
    hsize_t dims[H5S_MAX_RANK];
    int perm[H5S_MAX_RANK];
    H5T_class_t class_tmp;

    API_BEGIN;

    /* Validate input */
    if( NULL == tag_name || NULL == class_out || NULL == size_out || NULL == tstt_storage_out ||
        NULL == have_default_out || NULL == have_global_out || NULL == have_sparse_out )
    {
        mhdf_setFail( status, "Invalid input." );
        return;
    }

    /* Get group for tag */
    tag_id = get_tag( file_handle, tag_name, NULL, status );
    if( tag_id < 0 ) return;

    /* Check for sparse data */
    rval = mhdf_is_in_group( tag_id, SPARSE_ENTITY_NAME, status );
    if( rval < 0 )
    {
        H5Gclose( tag_id );
        return;
    }
    *have_sparse_out = rval ? 1 : 0;

    /* Check for variable-length tag data */
    rval = mhdf_find_attribute( tag_id, TAG_VARLEN_ATTRIB, &idx, status );
    if( rval < 0 )
    {
        H5Gclose( tag_id );
        return;
    }
    var_data = rval ? 1 : 0;

    /* Check if have default value for tag */
    rval = mhdf_find_attribute( tag_id, TAG_DEFAULT_ATTRIB, &idx, status );
    if( rval < 0 )
    {
        H5Gclose( tag_id );
        return;
    }
    if( !rval )
        *have_default_out = 0;
    else if( !var_data )
        *have_default_out = 1;
    else
    {
        /* *have_default_out = get_attrib_array_length_index( tag_id, index ); */
        *have_default_out = get_attrib_array_length_name( tag_id, TAG_DEFAULT_ATTRIB );
        if( *have_default_out < 0 )
        {
            mhdf_setFail( status, "Error checking length of default value for tag: %s\n", tag_name );
            H5Gclose( tag_id );
            return;
        }
    }

    /* Check if have global value for tag */
    rval = mhdf_find_attribute( tag_id, TAG_GLOBAL_ATTRIB, &idx, status );
    if( rval < 0 )
    {
        H5Gclose( tag_id );
        return;
    }
    if( !rval )
        *have_global_out = 0;
    else if( !var_data )
        *have_global_out = 1;
    else
    {
        /* *have_global_out = get_attrib_array_length_index( tag_id, index ); */
        *have_global_out = get_attrib_array_length_name( tag_id, TAG_GLOBAL_ATTRIB );
        if( *have_global_out < 0 )
        {
            mhdf_setFail( status, "Error checking length of global value for tag: %s\n", tag_name );
            H5Gclose( tag_id );
            return;
        }
    }

    /* Get TSTT tag class */
    rval = mhdf_read_scalar_attrib( tag_id, TAG_TYPE_ATTRIB, H5T_NATIVE_INT, tstt_storage_out, status );
    if( rval < 1 )
    {
        H5Gclose( tag_id );
        return;
    }

    /* Check if tag is storing entity handles */
    rval = mhdf_find_attribute( tag_id, TAG_HANDLE_TYPE_ATTRIB, &idx, status );
    if( rval < 0 )
    {
        H5Gclose( tag_id );
        return;
    }
    is_handle = rval;

    /* Get tag type */
#if defined( H5Topen_vers ) && H5Topen_vers > 1
    type_id = H5Topen2( tag_id, TAG_TYPE_NAME, H5P_DEFAULT );
#else
    type_id = H5Topen( tag_id, TAG_TYPE_NAME );
#endif
    if( type_id < 0 )
    {
        H5Gclose( tag_id );
        mhdf_setFail( status, "Failed to get type object for tag \"%s\".", tag_name );
        return;
    }

    class_tmp = H5Tget_class( type_id );
    if( class_tmp < 0 )
    {
        mhdf_setFail( status, "H5Tget_class failed." );
        H5Gclose( tag_id );
        H5Tclose( type_id );
        return;
    }

    size = H5Tget_size( type_id );
    if( size <= 0 )
    {
        mhdf_setFail( status, "H5Tget_size failed." );
        H5Gclose( tag_id );
        H5Tclose( type_id );
        return;
    }

    switch( class_tmp )
    {
        case H5T_INTEGER:
            *class_out = ( size == 1 ) ? mhdf_BOOLEAN : mhdf_INTEGER;
            *size_out  = 1;
            break;

        case H5T_FLOAT:
            *class_out = mhdf_FLOAT;
            *size_out  = 1;
            break;

        case H5T_BITFIELD:
            *class_out = mhdf_BITFIELD;
            *size_out  = H5Tget_precision( type_id );
            if( *size_out <= 0 )
            {
                mhdf_setFail( status, "H5Tget_precision failed." );
                H5Gclose( tag_id );
                H5Tclose( type_id );
                return;
            }
            break;

        default:
        case H5T_OPAQUE:
            *class_out = mhdf_OPAQUE;
            *size_out  = size;
            break;

        case H5T_ARRAY:

#if defined( H5Tget_array_dims_vers ) && H5Tget_array_dims_vers > 1
            (void)perm; /* suppress warning */
            rank = H5Tget_array_dims2( type_id, dims );
#else
            rank      = H5Tget_array_dims( type_id, dims, perm );
#endif
            if( rank <= 0 )
            {
                mhdf_setFail( status, "H5Tget_size failed." );
                H5Gclose( tag_id );
                H5Tclose( type_id );
                return;
            }
            for( i = 1; i < rank; ++i )
                dims[0] *= dims[i];

            super_id = H5Tget_super( type_id );
            if( super_id < 0 )
            {
                mhdf_setFail( status, "H5Tget_super failed" );
                H5Gclose( tag_id );
                H5Tclose( type_id );
                return;
            }

            class_tmp = H5Tget_class( super_id );
            if( class_tmp < 0 )
            {
                mhdf_setFail( status, "H5Tget_class failed." );
                H5Gclose( tag_id );
                H5Tclose( type_id );
                H5Tclose( super_id );
                return;
            }

            sup_size = H5Tget_size( super_id );
            H5Tclose( super_id );
            if( sup_size <= 0 )
            {
                mhdf_setFail( status, "H5Tget_size failed." );
                H5Gclose( tag_id );
                H5Tclose( type_id );
                return;
            }

            switch( class_tmp )
            {
                case H5T_INTEGER:
                    *class_out = ( sup_size == 1 ) ? mhdf_BOOLEAN : mhdf_INTEGER;
                    *size_out  = dims[0];
                    break;

                case H5T_FLOAT:
                    *class_out = mhdf_FLOAT;
                    *size_out  = dims[0];
                    break;

                default:
                    *class_out = mhdf_OPAQUE;
                    *size_out  = size;
                    break;
            }

            break;
    }
    H5Tclose( type_id );
    H5Gclose( tag_id );

    if( is_handle )
    {
        if( *class_out != mhdf_INTEGER )
        {
            mhdf_setFail( status, "Non-integer tag marked as handle type." );
            return;
        }
        *class_out = mhdf_ENTITY_ID;
    }

    if( var_data )
    {
        if( *size_out != 1 || *class_out == mhdf_BITFIELD )
        {
            mhdf_setFail( status, "Invalid or unexpected variable-length tag data" );
            return;
        }
        *size_out = -1;
    }

    mhdf_setOkay( status );
    API_END;
}

static int read_tag_attrib_data( hid_t tag_id,
                                 const char* attrib_name,
                                 hid_t type_id,
                                 void* data,
                                 int is_var_len,
                                 mhdf_Status* status )
{
    int rval, ilen;
    unsigned idx;
    hid_t read_type = type_id;
    hsize_t len;

    /* Check if tag has attribute */
    rval = mhdf_find_attribute( tag_id, attrib_name, &idx, status );
    if( rval < 0 )
        return 0;
    else if( 0 == rval )
        return 1;

    if( NULL == data )
    {
        mhdf_setFail( status, "Invalid input." );
        return 0;
    }

    if( is_var_len )
    {
        /* len = get_attrib_array_length_index(tag_id, index); */
        ilen = get_attrib_array_length_name( tag_id, attrib_name );
        if( ilen < 0 )
        {
            mhdf_setFail( status, "Failed to read length of default/mesh value for tag" );
            return 0;
        }
        len = ilen;

        /* caller passes type_id == 0 for OPAQUE */
        if( 0 == type_id )
            read_type = H5Tcreate( H5T_OPAQUE, len );
        else
        {
#if defined( H5Tarray_create_vers ) && H5Tarray_create_vers > 1
            read_type = H5Tarray_create2( type_id, 1, &len );
#else
            read_type = H5Tarray_create( type_id, 1, &len, 0 );
#endif
        }
        if( read_type < 0 )
        {
            mhdf_setFail( status, "Failed to read mesh/default value for tag" );
            return 0;
        }
    }

    rval = mhdf_read_scalar_attrib( tag_id, attrib_name, read_type, data, status );
    if( is_var_len ) H5Tclose( read_type );

    return rval;
}

void mhdf_getTagValues( mhdf_FileHandle file_handle,
                        const char* tag_name,
                        hid_t output_data_type,
                        void* default_value,
                        void* global_value,
                        mhdf_Status* status )
{
    hid_t tag_id;
    int rval, var_data;
    unsigned int idx;
    API_BEGIN;

    /* check args */
    if( NULL == tag_name || !*tag_name )
    {
        mhdf_setFail( status, "Invalid input." );
        return;
    }

    /* Get the tag group */
    tag_id = get_tag( file_handle, tag_name, NULL, status );
    if( tag_id < 0 ) return;

    /* Check for variable-length tag data */
    rval = mhdf_find_attribute( tag_id, TAG_VARLEN_ATTRIB, &idx, status );
    if( rval < 0 )
    {
        H5Gclose( tag_id );
        return;
    }
    var_data = rval ? 1 : 0;

    /* Read default value if present */
    rval = read_tag_attrib_data( tag_id, TAG_DEFAULT_ATTRIB, output_data_type, default_value, var_data, status );
    if( !rval )
    {
        H5Gclose( tag_id );
        return;
    }

    /* Read mesh value if present */
    rval = read_tag_attrib_data( tag_id, TAG_GLOBAL_ATTRIB, output_data_type, global_value, var_data, status );
    if( !rval )
    {
        H5Gclose( tag_id );
        return;
    }

    H5Gclose( tag_id );
    mhdf_setOkay( status );
    API_END;
}

int mhdf_haveDenseTag( mhdf_FileHandle file_handle, const char* tag_name, const char* type_handle, mhdf_Status* status )
{
    char* path;
    hid_t elem_id, group_id;
    FileHandle* file_ptr;
    int rval = 0;
    API_BEGIN;

    file_ptr = (FileHandle*)file_handle;
    if( !mhdf_check_valid_file( file_ptr, status ) ) return -1;

    if( type_handle == mhdf_node_type_handle() )
    {
#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
        elem_id = H5Gopen2( file_ptr->hdf_handle, NODE_GROUP, H5P_DEFAULT );
#else
        elem_id = H5Gopen( file_ptr->hdf_handle, NODE_GROUP );
#endif
        if( elem_id < 0 ) mhdf_setFail( status, "Could not open node group." );
    }
    else if( type_handle == mhdf_set_type_handle() )
    {
#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
        elem_id = H5Gopen2( file_ptr->hdf_handle, SET_GROUP, H5P_DEFAULT );
#else
        elem_id = H5Gopen( file_ptr->hdf_handle, SET_GROUP );
#endif
        if( elem_id < 0 ) mhdf_setFail( status, "Could not open set group." );
    }
    else
    {
        elem_id = mhdf_elem_group_from_handle( file_ptr, type_handle, status );
    }
    if( elem_id < 0 ) return -1;

    rval = mhdf_is_in_group( elem_id, TAG_GROUP_NAME, status );
    if( rval < 0 )
    {
        H5Gclose( elem_id );
        return -1;
    }
    else if( rval == 0 )
    {
        H5Gclose( elem_id );
        mhdf_setOkay( status );
        return 0;
    }

#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
    group_id = H5Gopen2( elem_id, DENSE_TAG_SUBGROUP, H5P_DEFAULT );
#else
    group_id = H5Gopen( elem_id, DENSE_TAG_SUBGROUP );
#endif
    H5Gclose( elem_id );
    if( group_id < 0 )
    {
        mhdf_setFail( status, "Could not open tag subgroup." );
        return -1;
    }

    path = mhdf_name_to_path_copy( tag_name, status );
    if( NULL == path )
    {
        H5Gclose( group_id );
        return -1;
    }

    rval = mhdf_is_in_group( group_id, path, status );
    H5Gclose( group_id );
    free( path );

    if( rval >= 0 )
    {
        mhdf_setOkay( status );
    }

    API_END;
    return rval;
}

hid_t mhdf_createDenseTagData( mhdf_FileHandle file_handle,
                               const char* tag_name,
                               const char* type_handle,
                               long num_values,
                               mhdf_Status* status )
{
    char* path;
    hid_t elem_id, data_id, type_id;
    FileHandle* file_ptr;
    size_t name_len, path_len, dir_len;
    hsize_t size;
    API_BEGIN;

    file_ptr = (FileHandle*)file_handle;
    if( !mhdf_check_valid_file( file_ptr, status ) ) return -1;

    if( type_handle == mhdf_node_type_handle() )
    {
#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
        elem_id = H5Gopen2( file_ptr->hdf_handle, NODE_GROUP, H5P_DEFAULT );
#else
        elem_id = H5Gopen( file_ptr->hdf_handle, NODE_GROUP );
#endif
        if( elem_id < 0 ) mhdf_setFail( status, "Could not open node group." );
    }
    else if( type_handle == mhdf_set_type_handle() )
    {
#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
        elem_id = H5Gopen2( file_ptr->hdf_handle, SET_GROUP, H5P_DEFAULT );
#else
        elem_id = H5Gopen( file_ptr->hdf_handle, SET_GROUP );
#endif
        if( elem_id < 0 ) mhdf_setFail( status, "Could not open set group." );
    }
    else
    {
        elem_id = mhdf_elem_group_from_handle( file_ptr, type_handle, status );
    }
    if( elem_id < 0 ) return -1;

    dir_len  = strlen( DENSE_TAG_SUBGROUP );
    name_len = mhdf_name_to_path( tag_name, NULL, 0 );
    path_len = dir_len + name_len + 1;
    path     = (char*)mhdf_malloc( path_len, status );
    if( NULL == path )
    {
        H5Gclose( elem_id );
        return -1;
    }
    strcpy( path, DENSE_TAG_SUBGROUP );
    mhdf_name_to_path( tag_name, path + dir_len, name_len + 1 );

    type_id = get_tag_type( file_ptr, path + dir_len, status );
    if( type_id < 0 )
    {
        H5Gclose( elem_id );
        return -1;
    }

    size    = (hsize_t)num_values;
    data_id = mhdf_create_table( elem_id, path, type_id, 1, &size, status );
    free( path );
    H5Gclose( elem_id );
    H5Tclose( type_id );

    if( data_id > 0 ) mhdf_setOkay( status );

    API_END_H( 1 );
    return data_id;
}

hid_t mhdf_openDenseTagData( mhdf_FileHandle file_handle,
                             const char* tag_name,
                             const char* type_handle,
                             long* num_values_out,
                             mhdf_Status* status )
{
    char* path;
    hid_t elem_id, data_id;
    FileHandle* file_ptr;
    size_t name_len, path_len, dir_len;
    hsize_t size;
    API_BEGIN;

    file_ptr = (FileHandle*)file_handle;
    if( !mhdf_check_valid_file( file_ptr, status ) ) return -1;

    if( type_handle == mhdf_node_type_handle() )
    {
#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
        elem_id = H5Gopen2( file_ptr->hdf_handle, NODE_GROUP, H5P_DEFAULT );
#else
        elem_id = H5Gopen( file_ptr->hdf_handle, NODE_GROUP );
#endif
        if( elem_id < 0 ) mhdf_setFail( status, "Could not open node group." );
    }
    else if( type_handle == mhdf_set_type_handle() )
    {
#if defined( H5Gopen_vers ) && H5Gopen_vers > 1
        elem_id = H5Gopen2( file_ptr->hdf_handle, SET_GROUP, H5P_DEFAULT );
#else
        elem_id = H5Gopen( file_ptr->hdf_handle, SET_GROUP );
#endif
        if( elem_id < 0 ) mhdf_setFail( status, "Could not open set group." );
    }
    else
    {
        elem_id = mhdf_elem_group_from_handle( file_ptr, type_handle, status );
    }
    if( elem_id < 0 ) return -1;

    dir_len  = strlen( DENSE_TAG_SUBGROUP );
    name_len = mhdf_name_to_path( tag_name, NULL, 0 );
    path_len = dir_len + name_len + 1;
    path     = (char*)mhdf_malloc( path_len, status );
    if( NULL == path )
    {
        H5Gclose( elem_id );
        return -1;
    }
    strcpy( path, DENSE_TAG_SUBGROUP );
    mhdf_name_to_path( tag_name, path + dir_len, name_len + 1 );

    data_id = mhdf_open_table( elem_id, path, 1, &size, status );
    free( path );
    H5Gclose( elem_id );
    *num_values_out = (long)size;

    if( data_id >= 0 ) mhdf_setOkay( status );

    API_END_H( 1 );
    return data_id;
}

void mhdf_createSparseTagData( mhdf_FileHandle file_handle,
                               const char* tag_name,
                               long num_values,
                               hid_t handles_out[2],
                               mhdf_Status* status )
{
    hid_t tag_id, index_id, data_id, type_id, id_type;
    hsize_t count = (hsize_t)num_values;
    API_BEGIN;

    tag_id = get_tag( file_handle, tag_name, &id_type, status );
    if( tag_id < 0 ) return;

#if defined( H5Topen_vers ) && H5Topen_vers > 1
    type_id = H5Topen2( tag_id, TAG_TYPE_NAME, H5P_DEFAULT );
#else
    type_id = H5Topen( tag_id, TAG_TYPE_NAME );
#endif
    if( type_id < 0 )
    {
        H5Gclose( tag_id );
        mhdf_setFail( status, "Failed to get type object for tag \"%s\".", tag_name );
        return;
    }

    index_id = mhdf_create_table( tag_id, SPARSE_ENTITY_NAME, id_type, 1, &count, status );
    if( index_id < 0 )
    {
        H5Gclose( tag_id );
        H5Tclose( type_id );
        return;
    }

    data_id = mhdf_create_table( tag_id, SPARSE_VALUES_NAME, type_id, 1, &count, status );
    H5Tclose( type_id );
    H5Gclose( tag_id );
    if( data_id < 0 )
    {
        H5Dclose( index_id );
        return;
    }

    handles_out[0] = index_id;
    handles_out[1] = data_id;
    mhdf_setOkay( status );
    API_END_H( 2 );
}

void mhdf_createVarLenTagData( mhdf_FileHandle file_handle,
                               const char* tag_name,
                               long num_entities,
                               long num_values,
                               hid_t handles_out[3],
                               mhdf_Status* status )
{
    hid_t tag_id, index_id, data_id, type_id, offset_id, id_type;
    hsize_t count = (hsize_t)num_entities;
    API_BEGIN;

    tag_id = get_tag( file_handle, tag_name, &id_type, status );
    if( tag_id < 0 ) return;

#if defined( H5Topen_vers ) && H5Topen_vers > 1
    type_id = H5Topen2( tag_id, TAG_TYPE_NAME, H5P_DEFAULT );
#else
    type_id = H5Topen( tag_id, TAG_TYPE_NAME );
#endif
    if( type_id < 0 )
    {
        H5Gclose( tag_id );
        mhdf_setFail( status, "Failed to get type object for tag \"%s\".", tag_name );
        return;
    }

    index_id = mhdf_create_table( tag_id, SPARSE_ENTITY_NAME, id_type, 1, &count, status );
    if( index_id < 0 )
    {
        H5Gclose( tag_id );
        H5Tclose( type_id );
        return;
    }

    offset_id = mhdf_create_table( tag_id, TAG_VAR_INDICES, MHDF_INDEX_TYPE, 1, &count, status );
    if( index_id < 0 )
    {
        H5Dclose( offset_id );
        H5Gclose( tag_id );
        H5Tclose( type_id );
        return;
    }

    count   = (hsize_t)num_values;
    data_id = mhdf_create_table( tag_id, SPARSE_VALUES_NAME, type_id, 1, &count, status );
    H5Tclose( type_id );
    H5Gclose( tag_id );
    if( data_id < 0 )
    {
        H5Dclose( offset_id );
        H5Dclose( index_id );
        return;
    }

    handles_out[0] = index_id;
    handles_out[1] = data_id;
    handles_out[2] = offset_id;
    mhdf_setOkay( status );
    API_END_H( 3 );
}

void mhdf_openSparseTagData( mhdf_FileHandle file_handle,
                             const char* tag_name,
                             long* num_entity_out,
                             long* num_values_out,
                             hid_t handles_out[3],
                             mhdf_Status* status )
{
    hid_t tag_id, index_id, data_id, offset_id = -1;
    hsize_t num_ent, data_size, num_data;
    int rval;
    unsigned idx;
    API_BEGIN;

    tag_id = get_tag( file_handle, tag_name, NULL, status );
    if( tag_id < 0 ) return;

    index_id = mhdf_open_table( tag_id, SPARSE_ENTITY_NAME, 1, &num_ent, status );
    if( index_id < 0 )
    {
        H5Gclose( tag_id );
        return;
    }

    data_id = mhdf_open_table( tag_id, SPARSE_VALUES_NAME, 1, &data_size, status );
    if( data_id < 0 )
    {
        H5Gclose( tag_id );
        H5Dclose( index_id );
        return;
    }

    /* check if tag is variable-lentgth */
    rval = mhdf_find_attribute( tag_id, TAG_VARLEN_ATTRIB, &idx, status );
    if( rval < 0 )
    {
        H5Gclose( tag_id );
        H5Dclose( index_id );
        H5Dclose( data_id );
        return;
    }

    /* If variable length... */
    if( rval )
    {
        offset_id = mhdf_open_table( tag_id, TAG_VAR_INDICES, 1, &num_data, status );
        if( offset_id < 0 )
        {
            H5Gclose( tag_id );
            H5Dclose( index_id );
            H5Dclose( data_id );
            return;
        }
    }
    /* Otherwise the number of values is the same as the size of the data table */
    else
    {
        num_data = data_size;
    }

    H5Gclose( tag_id );
    if( num_ent != num_data )
    {
        mhdf_setFail( status, "Data length mismatch for sparse tag data -- invalid file." );
        if( offset_id >= 0 ) H5Dclose( offset_id );
        H5Dclose( index_id );
        H5Dclose( data_id );
        return;
    }
    *num_entity_out = (long)num_ent;
    if( num_values_out ) *num_values_out = (long)data_size;

    handles_out[0] = index_id;
    handles_out[1] = data_id;
    if( offset_id >= 0 ) handles_out[2] = offset_id;
    mhdf_setOkay( status );
    API_END_H( 2 );
}

void mhdf_writeSparseTagEntities( hid_t table_id,
                                  long offset,
                                  long count,
                                  hid_t int_type,
                                  const void* id_list,
                                  mhdf_Status* status )
{
    API_BEGIN;
    mhdf_write_data( table_id, offset, count, int_type, id_list, H5P_DEFAULT, status );
    API_END;
}
void mhdf_writeSparseTagEntitiesWithOpt( hid_t table_id,
                                         long offset,
                                         long count,
                                         hid_t int_type,
                                         const void* id_list,
                                         hid_t io_prop,
                                         mhdf_Status* status )
{
    API_BEGIN;
    mhdf_write_data( table_id, offset, count, int_type, id_list, io_prop, status );
    API_END;
}

void mhdf_writeTagValues( hid_t table_id,
                          long offset,
                          long count,
                          hid_t tag_type,
                          const void* tag_data,
                          mhdf_Status* status )
{
    mhdf_writeTagValuesWithOpt( table_id, offset, count, tag_type, tag_data, H5P_DEFAULT, status );
}

void mhdf_writeTagValuesWithOpt( hid_t table_id,
                                 long offset,
                                 long count,
                                 hid_t tag_type,
                                 const void* tag_data,
                                 hid_t io_prop,
                                 mhdf_Status* status )
{
    API_BEGIN;
    mhdf_write_data( table_id, offset, count, tag_type, tag_data, io_prop, status );
    API_END;
}

void mhdf_writeSparseTagIndices( hid_t table_id,
                                 long offset,
                                 long count,
                                 hid_t int_type,
                                 const void* indices,
                                 mhdf_Status* status )
{
    API_BEGIN;
    mhdf_write_data( table_id, offset, count, int_type, indices, H5P_DEFAULT, status );
    API_END;
}
void mhdf_writeSparseTagIndicesWithOpt( hid_t table_id,
                                        long offset,
                                        long count,
                                        hid_t int_type,
                                        const void* indices,
                                        hid_t io_prop,
                                        mhdf_Status* status )
{
    API_BEGIN;
    mhdf_write_data( table_id, offset, count, int_type, indices, io_prop, status );
    API_END;
}

void mhdf_readSparseTagEntities( hid_t table_id,
                                 long offset,
                                 long count,
                                 hid_t int_type,
                                 void* id_list,
                                 mhdf_Status* status )
{
    API_BEGIN;
    mhdf_read_data( table_id, offset, count, int_type, id_list, H5P_DEFAULT, status );
    API_END;
}
void mhdf_readSparseTagEntitiesWithOpt( hid_t table_id,
                                        long offset,
                                        long count,
                                        hid_t int_type,
                                        void* id_list,
                                        hid_t io_prop,
                                        mhdf_Status* status )
{
    API_BEGIN;
    mhdf_read_data( table_id, offset, count, int_type, id_list, io_prop, status );
    API_END;
}

void mhdf_readTagValues( hid_t table_id, long offset, long count, hid_t tag_type, void* tag_data, mhdf_Status* status )
{
    mhdf_readTagValuesWithOpt( table_id, offset, count, tag_type, tag_data, H5P_DEFAULT, status );
}
void mhdf_readTagValuesWithOpt( hid_t table_id,
                                long offset,
                                long count,
                                hid_t tag_type,
                                void* tag_data,
                                hid_t io_prop,
                                mhdf_Status* status )
{
    API_BEGIN;
    mhdf_read_data( table_id, offset, count, tag_type, tag_data, io_prop, status );
    API_END;
}

void mhdf_readSparseTagIndices( hid_t table_id,
                                long offset,
                                long count,
                                hid_t int_type,
                                void* indices,
                                mhdf_Status* status )
{
    API_BEGIN;
    mhdf_read_data( table_id, offset, count, int_type, indices, H5P_DEFAULT, status );
    API_END;
}
void mhdf_readSparseTagIndicesWithOpt( hid_t table_id,
                                       long offset,
                                       long count,
                                       hid_t int_type,
                                       void* indices,
                                       hid_t io_prop,
                                       mhdf_Status* status )
{
    API_BEGIN;
    mhdf_read_data( table_id, offset, count, int_type, indices, io_prop, status );
    API_END;
}
